package com.up.mongo.config;

import cn.hutool.core.lang.Pair;
import cn.hutool.core.util.StrUtil;
import com.alibaba.cloud.nacos.NacosConfigManager;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.Listener;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.common.utils.StringUtils;
import com.up.mongo.properties.AutoCreateIndexLocalCatch;
import com.up.mongo.properties.AutoCreateIndexProperties;
import com.up.mongo.properties.IndexModule;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.data.annotation.Id;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.index.Index;
import org.springframework.data.mongodb.core.index.IndexField;
import org.springframework.data.mongodb.core.index.IndexInfo;

import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.*;
import java.util.stream.Collectors;

/**
 * @author: liuchengjun
 * @title: AutoCreateIndexesManager
 * @projectName: 海联技术中台V2.0
 * @depart 软件事业部
 * @description:
 * @date: 2024/3/6/006 10:05 (星期三)
 * @since V2.0
 */
public class AutoCreateIndexesManager implements SmartInitializingSingleton {
    private final Logger logger = LoggerFactory.getLogger(AutoCreateIndexesManager.class);
    private final ConfigService configService;
    private final MongoTemplate mongoTemplate;

    private final AutoCreateIndexProperties indexProperties;
    private final Set<String> hasAddListener = new CopyOnWriteArraySet<>();
    public AutoCreateIndexesManager(NacosConfigManager nacosConfigManager, MongoTemplate mongoTemplate, AutoCreateIndexProperties indexProperties) {
        this.configService = nacosConfigManager.getConfigService();
        this.mongoTemplate = mongoTemplate;
        this.indexProperties = indexProperties;
    }
    @Override
    public void afterSingletonsInstantiated() {
        addListener();
    }

    public void addListener() {
        if (!hasAddListener.add(indexProperties.getDataId())) {
            return;
        }
        checkConfig();
        //创建监听器，监听指定配置文件变更
        Listener listener = new Listener() {
            @Override
            public Executor getExecutor() {
                ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 4, 60, TimeUnit.SECONDS,
                        new LinkedBlockingQueue<>(100), new ThreadPoolExecutor.CallerRunsPolicy());
                executor.allowCoreThreadTimeOut(true);
                return executor;
            }

            @Override
            public void receiveConfigInfo(String configInfo) {
                logger.info("AutoCreateIndexesManager#receiveConfigInfo receive configInfo. configInfo={}", configInfo);
                triggerConfig(configInfo);
            }
        };
        String dataId = indexProperties.getDataId();
        try {
            String group = indexProperties.getConfigGroup();
            String config = configService.getConfig(indexProperties.getDataId(), group, 60);
            logger.info("AutoCreateIndexesManager#afterPropertiesSet init configInfo. configInfo={}", config);
            // 初始化
            listener.getExecutor().execute(() -> triggerConfig(config));
            // 监听
            configService.addListener(dataId, group, listener);
        } catch (NacosException e) {
            e.printStackTrace();
            throw new RuntimeException("nacos server 监听 异常! dataId = " + dataId);
        }
    }

    private void checkConfig() {
        if (!indexProperties.isEnable()) {
            throw new RuntimeException("开启了mongodb的索引自动创建，出现非法错误");
        }
        IndexModule module = indexProperties.getModule();
        if (module == null) {
            throw new RuntimeException("开启了mongodb的索引自动创建，需要配置当前模块的名称,取值范围:[" + StringUtils.join(Arrays.asList(IndexModule.values()), ",") + "]");
        }
        String group = indexProperties.getConfigGroup();
        String dataId = indexProperties.getDataId();
        if (!StrUtil.isAllNotEmpty(group, dataId)) {
            throw new RuntimeException("开启了mongodb的索引自动创建，配置中心的配置ID和分组不能为空");
        }
    }

    private void triggerConfig(String configInfo) {

        try {
            List<AutoCreateIndexLocalCatch> catches = JSONObject.parseArray(configInfo, AutoCreateIndexLocalCatch.class);
            if (catches == null || catches.isEmpty()) {
                return;
            }
            //取当前模块
            IndexModule module = this.indexProperties.getModule();
            catches.stream()
                    .filter(index -> index.getModule().equals(module) && index.getIndexes() != null)
                    .forEach(index -> {
                        List<AutoCreateIndexLocalCatch.Indexes> indexes = index.getIndexes();
                        //配置文件-文档-文档索引
                        Map<Class<?>, List<Pair<AutoCreateIndexLocalCatch.Indexes, String>>> entityToFields = new HashMap<>();
                        for (AutoCreateIndexLocalCatch.Indexes indexCatch : indexes) {
                            if (!indexCheck(indexCatch)) {
                                continue;
                            }
                            String entityName = indexCatch.getEntityName();
                            try {
                                Class<?> entityClass = Class.forName(entityName);
                                String primaryKey = null;
                                List<String> indexFields = indexCatch.getIndexFields();
                                if (indexFields != null && !indexFields.isEmpty()) {
                                    for (String indexField : indexCatch.getIndexFields()) {
                                        Field declaredField = entityClass.getDeclaredField(indexField);
                                        if (declaredField.isAnnotationPresent(Id.class)) {
                                            primaryKey = declaredField.getName();
                                        }
                                    }
                                }
                                if (primaryKey == null) {
                                    for (Field field : entityClass.getDeclaredFields()) {
                                        if (field.isAnnotationPresent(Id.class)) {
                                            primaryKey = field.getName();
                                        }
                                    }
                                }
                                List<Pair<AutoCreateIndexLocalCatch.Indexes, String>> computed = entityToFields.computeIfAbsent(entityClass, key -> new ArrayList<>());
                                computed.add(new Pair<>(indexCatch, primaryKey));
                            } catch (ClassNotFoundException | NoSuchFieldException ignored) {
                            }
                        }
                        for (Map.Entry<Class<?>, List<Pair<AutoCreateIndexLocalCatch.Indexes, String>>> entry : entityToFields.entrySet()) {
                            Class<?> entityClass = entry.getKey();
                            List<Pair<AutoCreateIndexLocalCatch.Indexes, String>> value = entry.getValue();
                            Pair<AutoCreateIndexLocalCatch.Indexes, String> tmpKey = value.get(0);
                            String primaryKey = tmpKey.getValue();
                            //数据库-当前文档-索引
                            List<IndexInfo> indexInfos = this.mongoTemplate.indexOps(entityClass).getIndexInfo();
                            indexInfos.removeIf(info -> {
                                String name = info.getName();
                                if (name.equals("_id_")) {
                                    return true;
                                }
                                List<IndexField> indexFields = info.getIndexFields();
                                if (indexFields.size() == 1) {
                                    IndexField indexField = indexFields.get(0);
                                    if (indexField.getKey().equals("_id")) {
                                        return true;
                                    }
                                    return StringUtils.isNotEmpty(primaryKey) && primaryKey.equals(indexField.getKey());
                                }
                                return false;
                            });
                            List<AutoCreateIndexLocalCatch.Indexes> indexesList = value.stream().map(Pair::getKey).collect(Collectors.toList());
                            indexesList.removeIf(item -> item.getIndexFields() == null || item.getIndexFields().isEmpty());
                            Set<String> configSet = new HashSet<>();
                            for (AutoCreateIndexLocalCatch.Indexes in : indexesList) {
                                configSet.add(in.getIndexName());
                            }
                            Set<String> dataSet = new HashSet<>();
                            for (IndexInfo indexInfo : indexInfos) {
                                dataSet.add(indexInfo.getName());
                            }
                            List<AutoCreateIndexLocalCatch.Indexes> addIndexList = new ArrayList<>();
                            List<String> deleteIndexList = new ArrayList<>();
                            for (IndexInfo indexInfo : indexInfos) {
                                String indexName = indexInfo.getName();
                                if(!configSet.contains(indexName)){
                                    //已经删除
                                    deleteIndexList.add(indexName);
                                }
                            }
                            for (AutoCreateIndexLocalCatch.Indexes configIndex : indexesList) {
                                String indexName = configIndex.getIndexName();
                                if(!dataSet.contains(indexName)){
                                    //新增
                                    addIndexList.add(configIndex);
                                }
                            }
                            for (String indexName : deleteIndexList) {
                                this.mongoTemplate.indexOps(entityClass).dropIndex(indexName);
                            }
                            for (AutoCreateIndexLocalCatch.Indexes addIndex : addIndexList) {
                                Index mIndex = new Index();
                                if (addIndex.isUnique()) {
                                    mIndex.unique();
                                }
                                Sort.Direction direction = addIndex.getDirection();
                                if (direction == null) {
                                    direction = Sort.Direction.ASC;
                                }
                                for (String indexField : addIndex.getIndexFields()) {
                                    mIndex.on(indexField, direction);
                                }
                                mIndex.named(addIndex.getIndexName());
                                this.mongoTemplate.indexOps(entityClass).ensureIndex(mIndex);
                            }
                        }
                    });
        } catch (Exception ignored) {
        }
    }

    private String getSwap(List<String> indexFields, String primaryKey) {
        StringBuilder builder = new StringBuilder();
        for (String indexField : indexFields) {
            if (indexField.equals(primaryKey)) {
                builder.append("_id");
            } else {
                builder.append(indexField);
            }
            builder.append(",");
        }

        return builder.delete(builder.length() - 1, builder.length()).toString();
    }

    private Pair<String, Boolean> removeSameIndex(IndexInfo info, List<AutoCreateIndexLocalCatch.Indexes> indexesList, Map<String, String> indexNameToSwap) {
        List<String> existsFields = info.getIndexFields().stream().map(IndexField::getKey).collect(Collectors.toList());
        String existsFieldsJoin = StringUtils.join(existsFields, ",");
        boolean isRemove = false;
        for (Map.Entry<String, String> entry : indexNameToSwap.entrySet()) {
            if (entry.getValue().equals(existsFieldsJoin)) {
                String key = entry.getKey();
                AutoCreateIndexLocalCatch.Indexes remove = null;
                for (AutoCreateIndexLocalCatch.Indexes indexes : indexesList) {
                    if (indexes.getIndexName().equals(key) && indexes.isUnique() == info.isUnique()) {
                        remove = indexes;
                        break;
                    }
                }
                indexesList.remove(remove);
                isRemove = true;
            }
        }
        return new Pair<>(existsFieldsJoin, isRemove);
    }

    private boolean indexCheck(AutoCreateIndexLocalCatch.Indexes indexCatch) {
        return StrUtil.isAllNotEmpty(indexCatch.getIndexName(), indexCatch.getEntityName());
    }

}
